#if defined(__GNUC__)

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>

#define DIR_ACL 0777
#define FILE_ACL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

static char* separatorString = (char*) "/";
static char separatorChar = '/';

static char* sourceDirectory;
static char* destinationDirectory;
static bool preserveDeletedFiles;
static long copied;
static long deleted;
static long skipped;

//file structure
struct INODE_STRUCTURE {
	char fileName[__DARWIN_MAXPATHLEN];
};

// helpful directory's files container
class Files {
	long size;

public:
	long count;
	INODE_STRUCTURE * list;

	Files() {
		size = 16;
		count = 0;
		list = (INODE_STRUCTURE*) malloc(sizeof(INODE_STRUCTURE) * size);
	}
	~Files() {
		free(list);
	}

	void add(INODE_STRUCTURE * file) {
		if (file) {
			if (count >= size) {
				size += 32;
				list = (INODE_STRUCTURE*) realloc(list, sizeof(INODE_STRUCTURE) * size);
			}
			memcpy(list + count, file, sizeof(INODE_STRUCTURE));
			count++;
		}
	}

	void sort() {
		INODE_STRUCTURE tmp;
		for (long i = 0; i < count - 1; i++) {
			for (long j = i; j < count; j++) {
				int cmp = strcmp(list[i].fileName, list[j].fileName);
				if (cmp > 0) {
					memcpy(&tmp, list + i, sizeof(INODE_STRUCTURE));
					memcpy(list + i, list + j, sizeof(INODE_STRUCTURE));
					memcpy(list + j, &tmp, sizeof(INODE_STRUCTURE));
				}
			}
		}
	}
};

void printError(const char* text, const char* file) {
	printf("%s%s\n       %s\n", (text == NULL) ? "" : text, (file == NULL) ? "" : file, strerror(errno));
}

void listDir(char* dir, Files* files) {
	DIR *dp;
	if ((dp = opendir(dir)) != NULL) {
		struct dirent *dirp;
		while ((dirp = readdir(dp)) != NULL) {
			//skip current and previous directory
			const char* d_name = dirp->d_name;
			if (d_name[0] == '.' && (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0))) {
				continue;
			}
			struct INODE_STRUCTURE inode;
			strcpy(inode.fileName, d_name);
			files->add(&inode);
		}
		closedir(dp);
	}
	files->sort();
}

bool isDir(const char* dirName) {
	DIR* dir = opendir(dirName);
	if (dir) {
		closedir(dir);
		return true;
	} else {
		return false;
	}
}

bool DeleteDirectory(char* lpPathName) {
	Files * files = new Files();
	listDir(lpPathName, files);

	const size_t len = strlen(lpPathName) + __DARWIN_MAXPATHLEN + 8;
	char* file = (char*) malloc(sizeof(char) * len);

	INODE_STRUCTURE * list = files->list;
	for (int i = 0; i < files->count; i++) {
		INODE_STRUCTURE* info = &list[i];

		strncpy(file, lpPathName, len);
		strncat(file, separatorString, len);
		strncat(file, info->fileName, len);

		if (isDir(file)) {
			if (!DeleteDirectory(file)) {
				printError("Cannot delete dir: ", file);
			}
		} else {
			if (!unlink(file)) {
				deleted++;
			} else {
				printError("Cannot delete: ", file);
			}
		}
	}
	free(file);
	delete files;

	if (rmdir(lpPathName) == 0) {
		return true;
	} else {
		printError("Cannot delete dir: ", lpPathName);
		return false;
	}
}

void logCoping(const char* fileName) {
	const size_t len = strlen(fileName);
	if (len < 71) {
		printf("Coping: %-71s\r", fileName);
	} else {
		printf("Coping: %.34s...%.34s\r", fileName, fileName + len - 34);
	}
}

bool CreateDirectory(const char * dirName) {
	if (isDir(dirName)) {
		if (chmod(dirName, DIR_ACL)) {
			printError("Cannot change dir: ", dirName);
		}
		return true;
	}
	if (mkdir(dirName, DIR_ACL)) {
		printError("Cannot create dir: ", dirName);
		return false;
	}
	return true;
}

bool CopyFile(const char * sourceFile, const char * destinationFile) {
	int srcDescriptor, destDescriptor, readBytes;
	const int BUFFERSIZE = 1024*1024;
	char buffer[BUFFERSIZE];

	if ((srcDescriptor = open(sourceFile, O_RDONLY)) == -1) {
		printError("Cannot open ", sourceFile);
		return false;
	}

	if ((destDescriptor = open(destinationFile, O_CREAT | O_TRUNC | O_WRONLY)) == -1) {
		printError("Cannot create ", destinationFile);
		return false;
	}

	while ((readBytes = read(srcDescriptor, buffer, BUFFERSIZE)) > 0) {
		if (write(destDescriptor, buffer, readBytes) != readBytes) {
			printError("Write error to ", destinationFile);
			close(srcDescriptor);
			close(destDescriptor);
			return false;
		}
	}

	if (readBytes == -1) {
		printError("Read error from ", sourceFile);
		close(srcDescriptor);
		close(destDescriptor);
		return false;
	}

	//set change time to be the same
	struct stat fileStatistic;
	if (fstat(srcDescriptor, &fileStatistic) != -1) {
		//change destination rights
		fchmod(destDescriptor, fileStatistic.st_mode | FILE_ACL);

		//change destination time stamp
		time_t file1time = fileStatistic.st_mtime;
		struct timeval times[2];
		times[0].tv_sec = file1time;
		times[0].tv_usec = 0;
		times[1].tv_sec = file1time;
		times[1].tv_usec = 0;
		if (utimes(destinationFile, times) == -1) {
			printError("Cannot set time for: ", destinationFile);
		}
	} else {
		//change destination rights
		fchmod(destDescriptor, FILE_ACL);
		printError("Cannot take time for: ", sourceFile);
	}

	if (close(srcDescriptor) == -1) {
		printError("Error closing file", sourceFile);
	}

	if (close(destDescriptor) == -1) {
		printError("Error closing file", destinationFile);
	}

	return true;
}

void synch(char* sourceDir, char* destinationDir) {
	Files * srcFiles = new Files();
	listDir(sourceDir, srcFiles);

	Files * destFiles = new Files();
	listDir(destinationDir, destFiles);

	long s = 0, d = 0;
	const long sSize = srcFiles->count;
	const long dSize = destFiles->count;

	const size_t len1 = strlen(sourceDir) + __DARWIN_MAXPATHLEN + 8;
	char* file1 = (char*) malloc(sizeof(char) * len1);

	const size_t len2 = strlen(destinationDir) + __DARWIN_MAXPATHLEN + 8;
	char* file2 = (char*) malloc(sizeof(char) * len2);

	while (s < sSize || d < dSize) {
		INODE_STRUCTURE * srcDATA = (s < sSize) ? (srcFiles->list + s) : NULL;
		INODE_STRUCTURE * destDATA = (d < dSize) ? (destFiles->list + d) : NULL;

		if (s < sSize && d < dSize) {
			//need to analyze differences in the directory entries

			int cmp = strcmp(srcDATA->fileName, destDATA->fileName);

			strncpy(file1, sourceDir, len1);
			strncat(file1, separatorString, len1);
			strncat(file1, srcDATA->fileName, len1);

			if (cmp < 0) {
				//new file

				strncpy(file2, destinationDir, len2);
				strncat(file2, separatorString, len2);
				strncat(file2, srcDATA->fileName, len2);

				if (isDir(file1)) {
					CreateDirectory(file2);
					synch(file1, file2);
				} else {
					logCoping(file1);
					if (CopyFile(file1, file2)) {
						copied++;
					} else {
						printError("Cannot copy: ", file1);
					}
				}
				s++;
			} else if (cmp == 0) {
				//same entry

				strncpy(file2, destinationDir, len2);
				strncat(file2, separatorString, len2);
				strncat(file2, srcDATA->fileName, len2);

				if (isDir(file1)) {
					CreateDirectory(file2);
					synch(file1, file2);
				} else {
					struct stat statbuf1;
					if (stat(file1, &statbuf1) != -1) {
						time_t file1time = statbuf1.st_mtime;
						struct stat statbuf2;
						if (stat(file2, &statbuf2) != -1) {
							time_t file2time = statbuf2.st_mtime;

							if (file1time != file2time) {
								logCoping(file1);
								if (CopyFile(file1, file2)) {
									copied++;
								} else {
									printError("Cannot overwrite: ", file2);
								}
							} else {
								skipped++;
							}

						} else {
							printError("Cannot take time for: ", file2);
						}
					} else {
						printError("Cannot take time for: ", file1);
					}
				}
				s++;
				d++;
			} else {
				// entry was deleted in source dir
				if (!preserveDeletedFiles) {
					//remove if persist differences was not specified

					strncpy(file2, destinationDir, len2);
					strncat(file2, separatorString, len2);
					strncat(file2, destDATA->fileName, len2);

					if (isDir(file2)) {
						if (!DeleteDirectory(file2)) {
							printError("Cannot delete dir: ", file2);
						}
					} else {
						if (!unlink(file2)) {
							deleted++;
						} else {
							printError("Cannot delete: ", file2);
						}
					}
				}
				d++;
			}
		} else if (d == dSize) {
			// source directory has more entries
			// just copy them to the destination
			strncpy(file1, sourceDir, len1);
			strncat(file1, separatorString, len1);
			strncat(file1, srcDATA->fileName, len1);

			strncpy(file2, destinationDir, len2);
			strncat(file2, separatorString, len2);
			strncat(file2, srcDATA->fileName, len2);

			if (isDir(file1)) {
				CreateDirectory(file2);
				synch(file1, file2);
			} else {
				logCoping(file1);
				if (CopyFile(file1, file2)) {
					copied++;
				} else {
					printError("Cannot copy: ", file1);
				}
			}
			s++;
		} else if (s == sSize) {
			// destination directory has more entries
			//remove them if persistence was not set
			if (!preserveDeletedFiles) {
				strncpy(file2, destinationDir, len2);
				strncat(file2, separatorString, len2);
				strncat(file2, destDATA->fileName, len2);

				if (isDir(file2)) {
					DeleteDirectory(file2);
				} else {
					if (!unlink(file2)) {
						deleted++;
					} else {
						printError("Cannot delete: ", file2);
					}
				}
				d++;
			} else {
				//preserve changes in destination
				d = dSize;
			}
		}
	}

	free(file1);
	free(file2);
	delete srcFiles;
	delete destFiles;
}

void mirror() {
	{//src name validation
		size_t len = strlen(sourceDirectory);
		if (len <= 0) {
			printf("Source directory is empty string\n");
			return;
		} else {
			len--;
			if (sourceDirectory[len] == separatorChar) {
				sourceDirectory[len] = 0;
			}
		}
	}
	{//dest name validation
		size_t len = strlen(destinationDirectory);
		if (len <= 0) {
			printf("Destination Directory is empty string\n");
			return;
		} else {
			len--;
			if (destinationDirectory[len] == separatorChar) {
				destinationDirectory[len] = 0;
			}
		}
	}

	//is dir sourceDirecroty
	if (!isDir(sourceDirectory)) {
		printError("Source directory is not a directory: ", sourceDirectory);
		return;
	}

	//is dir destinationDirecroty
	if (!isDir(destinationDirectory)) {
		//try to create dir
		if (!CreateDirectory(destinationDirectory)) {
			printError("cannot create destination directory: ", destinationDirectory);
			return;
		}
	}

	synch(sourceDirectory, destinationDirectory);
	printf("%ld copied, %ld skipped, %ld deleted %180s\n", copied, skipped, deleted, "");
}

void printHelp() {
	printf("usage: mirror [-p] sourceDir destinationDir\n");
	printf("       Make the content of destinationDir to be the same as sourceDir.\n");
	printf("       Avoid coping of same files\n");
	printf("    -p Preserve files in destinationDir that are missing in sourceDir\n");
}

// entry point for Borland C
int main(int argc, char* argv[]) {
	char arg1[__DARWIN_MAXPATHLEN];
	char arg2[__DARWIN_MAXPATHLEN];

	if ((argc == 4 && strcmp("-p", argv[1]) == 0) || argc == 3) {
		preserveDeletedFiles = (argc == 4);
		strncpy(arg2, argv[argc - 2], __DARWIN_MAXPATHLEN);
		sourceDirectory = arg2;
		strncpy(arg1, argv[argc - 1], __DARWIN_MAXPATHLEN);
		destinationDirectory = arg1;

		printf("---- MIRROR ----\n");
		printf("Source Dir      [%s]\n", sourceDirectory);
		printf("Destination Dir [%s] ", destinationDirectory);
		if (argc == 4) {
			printf(" preserve files");
		}
		printf("\n");

		// do the synchronization
		mirror();

	} else {
		printHelp();
	}
	return 0;
}

// entry point for Visual C
int wmain(int argc, char* argv[]) {
	if ((argc == 4 && strcmp("-p", argv[1]) == 0) || argc == 3) {
		preserveDeletedFiles = (argc == 4);
		sourceDirectory = argv[argc - 2];
		destinationDirectory = argv[argc - 1];

		printf("---- MIRROR ----\n");
		printf("Source Dir      [%s]\n", sourceDirectory);
		printf("Destination Dir [%s] ", destinationDirectory);
		if (argc == 4) {
			printf(" preserve files");
		}
		printf("\n");

		// do the synchronization
		mirror();

	} else {
		printHelp();
	}
	return 0;
}

#endif
